// Copyright (c) 2022 Huawei Device Co., Ltd. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/cert/cert_verify_proc_ohos.h"
#include <string>
#include <vector>
#include "base/logging.h"
#include "net/cert/cert_net_fetcher.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "openssl/err.h"
#include "openssl/ossl_typ.h"
#include "openssl/x509.h"
#include "openssl/x509_vfy.h"

#define ROOT_CERT "/etc/ssl/certs/cacert.pem"
#define MIN_CERT_NUM 1
namespace net {
CertVerifyProcOHOS::CertVerifyProcOHOS(
    scoped_refptr<CertNetFetcher> cert_net_fetcher)
    : cert_net_fetcher_(std::move(cert_net_fetcher)) {}

CertVerifyProcOHOS::~CertVerifyProcOHOS() {}

bool CertVerifyProcOHOS::SupportsAdditionalTrustAnchors() const {
  return false;
}

void GetChainDEREncodedBytes(X509Certificate* cert,
                             std::vector<std::string>* chain_bytes) {
  chain_bytes->reserve(1 + cert->intermediate_buffers().size());
  chain_bytes->emplace_back(
      net::x509_util::CryptoBufferAsStringPiece(cert->cert_buffer()));
  for (const auto& handle : cert->intermediate_buffers()) {
    chain_bytes->emplace_back(
        net::x509_util::CryptoBufferAsStringPiece(handle.get()));
  }
}

void X509_d2i_free(X509* server_cert[], uint32_t server_cert_sum) {
  uint32_t i;
  for (i = 0; i < server_cert_sum; i++) {
    X509_free(server_cert[i]);
  }
}

bool CertChainVerify(X509* server_cert[],
                     uint32_t server_cert_sum,
                     X509_STORE* root_store) {
  uint32_t i;
  STACK_OF(X509)* ca_stack = nullptr;
  X509_STORE_CTX* ctx = nullptr;

  // certificate chain verify
  for (i = server_cert_sum - 1; i > 0; i--) {
    // Create certificate store context function
    ctx = X509_STORE_CTX_new();
    if (ctx == nullptr) {
      LOG(ERROR) << "Create certificate store context function failed";
      X509_d2i_free(server_cert, server_cert_sum);
      X509_STORE_free(root_store);
      return false;
    }

    // Initialize the certificate chain
    X509_STORE_CTX_init(ctx, root_store, server_cert[i], ca_stack);

    // If verification fails, for detailed error information, see
    // X509_verify_cert_error_string
    if (!X509_verify_cert(ctx)) {
      LOG(ERROR) << "Certificate verify error: " << ctx->error
                 << "\nCertificate verify info: "
                 << X509_verify_cert_error_string(ctx->error)
                 << "\nTotal number of server certificates: " << server_cert_sum
                 << "\nserver certificates count: " << i;
      X509_d2i_free(server_cert, server_cert_sum);
      X509_STORE_CTX_free(ctx);
      X509_STORE_free(root_store);
      return false;
    }

    X509_STORE_CTX_free(ctx);
  }

  X509_d2i_free(server_cert, server_cert_sum);
  X509_STORE_free(root_store);

  return true;
}

bool CertVerify(std::vector<std::string>& cert_bytes) {
  uint32_t server_cert_sum;
  const unsigned char* der_encoded_tmp = nullptr;
  uint32_t i;
  int root_cert_sum = 0;
  X509_STORE* root_store = nullptr;
  X509_LOOKUP* look_up = nullptr;

  server_cert_sum = cert_bytes.size();
  if (server_cert_sum < MIN_CERT_NUM) {
    LOG(ERROR) << "Total number of server certificates is error";
    return false;
  }

  // Convert cert_bytes to internal X509 data structure(server_cert[]), If the
  // conversion fails, see ERR_reason_error_string()/kLibraryNames
  X509* server_cert[server_cert_sum];
  for (i = 0; i < server_cert_sum; i++) {
    der_encoded_tmp = (unsigned char*)cert_bytes[i].c_str();
    server_cert[i] = d2i_X509(nullptr, &der_encoded_tmp, cert_bytes[i].size());
    if (server_cert[i] == nullptr) {
      LOG(ERROR)
          << "Server certificate DEREncoded converted to X509 error, Reason: "
          << ERR_reason_error_string(ERR_get_error())
          << "\nTotal number of server certificates: " << server_cert_sum
          << "\nError certificate count:" << i;
      ERR_clear_error();
      X509_d2i_free(server_cert, i);
      return false;
    }
  }

  // Create X509 certificate store
  root_store = X509_STORE_new();
  if (root_store == nullptr) {
    LOG(ERROR) << "Create X509 certificate store failed";
    X509_d2i_free(server_cert, server_cert_sum);
    return false;
  }

  // Create X509_LOOKUP, the store_ctx member of this data structure is
  // associated with the newly created certificate store root_store
  look_up = X509_STORE_add_lookup(root_store, X509_LOOKUP_file());
  if (look_up == nullptr) {
    LOG(ERROR) << "Create X509 LOOKUP failed";
    X509_d2i_free(server_cert, server_cert_sum);
    X509_STORE_free(root_store);
    return false;
  }

  // Parse the root certificate file
  root_cert_sum =
      X509_load_cert_crl_file(look_up, ROOT_CERT, X509_FILETYPE_PEM);
  if (root_cert_sum == 0) {
    LOG(ERROR) << "Root certificate number is 0";
    X509_d2i_free(server_cert, server_cert_sum);
    X509_STORE_free(root_store);
    return false;
  }

  return CertChainVerify(server_cert, server_cert_sum, root_store);
}

int CertVerifyProcOHOS::VerifyInternal(
    X509Certificate* cert,
    const std::string& hostname,
    const std::string& ocsp_response,
    const std::string& sct_list,
    int flags,
    CRLSet* crl_set,
    const CertificateList& additional_trust_anchors,
    CertVerifyResult* verify_result,
    const NetLogWithSource& net_log) {
  std::vector<std::string> cert_bytes;

  GetChainDEREncodedBytes(cert, &cert_bytes);

  if (!CertVerify(cert_bytes)) {
    return ERR_FAILED;
  }

  return OK;
}

}  // namespace net